package org.loong.crypto.core.algorithm;

import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.loong.crypto.core.Algorithm;
import org.loong.crypto.core.AlgorithmFamily;

/**
 * The KeyPair algorithm.
 */
public final class KeyPairAlgorithm extends Algorithm {

    /**
     * The key length, zero if not specified.
     */
    private int keyLength;

    /**
     * DSA algorithm.
     * 
     * <p>
     * strength must be from 512 - 1024 and a multiple of 64
     * </p>
     */
    public static final KeyPairAlgorithm DSA = new KeyPairAlgorithm("DSA", X9ObjectIdentifiers.id_dsa, 512);

    /**
     * ECDSA algorithm.
     */
    public static final KeyPairAlgorithm ECDSA = new KeyPairAlgorithm("ECDSA", X9ObjectIdentifiers.id_ecPublicKey, 192);

    /**
     * RSA algorithm.
     */
    public static final KeyPairAlgorithm RSA = new KeyPairAlgorithm("RSA", PKCSObjectIdentifiers.rsaEncryption, 2048);

    /**
     * SM2 algorithm.
     */
    public static final KeyPairAlgorithm SM2 = new KeyPairAlgorithm("SM2", GMObjectIdentifiers.sm2p256v1, 256);
    
    /**
     * The supported key pair algorithms.
     */
    public static final Map<String, KeyPairAlgorithm> SUPPORTED_ALGORITHMS;

    static {
        Map<String, KeyPairAlgorithm> algs = new LinkedHashMap<>();
        algs.put(DSA.getName(), DSA);
        algs.put(ECDSA.getName(), ECDSA);
        algs.put(RSA.getName(), RSA);
        algs.put(SM2.getName(), SM2);
        SUPPORTED_ALGORITHMS = Collections.unmodifiableMap(algs);
    }

    /**
     * KeyPair algorithm family.
     */
    public static final class Family extends AlgorithmFamily<KeyPairAlgorithm> {

        private static final long serialVersionUID = 1L;

        /**
         * Creates a new key pair algorithm family.
         *
         * @param algs The key pair algorithms of the family. Must not be {@code null}.
         */
        public Family(final KeyPairAlgorithm... algs) {
            super(algs);
        }
    }

    /**
     * Creates a new key pair algorithm name.
     * 
     * @param name the algorithm name. Must not be {@code null}.
     * @param oid the algorithm object identifier.
     */
    public KeyPairAlgorithm(final String name, final ASN1ObjectIdentifier oid) {
        this(name, oid, 0);
    }

    /**
     * Creates a new key pair algorithm name.
     * 
     * @param name the algorithm name. Must not be {@code null}.
     * @param oid the algorithm object identifier.
     * @param keyLength the key length, zero if not specified.
     */
    public KeyPairAlgorithm(final String name, final ASN1ObjectIdentifier oid, final int keyLength) {
        super(name, oid);

        this.keyLength = keyLength;
    }

    /**
     * Gets the value of keyLength.
     *
     * @return the value of keyLength
     */
    public int getKeyLength() {
        return keyLength;
    }

    /**
     * Parses a key pair algorithm from the specified string.
     *
     * @param name the algorithm name. Must not be {@code null}.
     * @return the key pair algorithm.
     * @throws NoSuchAlgorithmException the ${@link NoSuchAlgorithmException}
     */
    public static KeyPairAlgorithm find(final String name) throws NoSuchAlgorithmException {
        KeyPairAlgorithm algorithm = SUPPORTED_ALGORITHMS.get(name);
        if (algorithm == null) {
            throw new NoSuchAlgorithmException("Unknown key pair algorithm type requested: " + name);
        }

        return algorithm;
    }
    
    /**
     * Find a key pair algorithm from the specified string.
     *
     * @param algorithmId the key pair algorithm id. Must not be {@code null}.
     * @return the key pair algorithm.
     * @throws NoSuchAlgorithmException the ${@link NoSuchAlgorithmException}
     */
    public static KeyPairAlgorithm findByAlgorithmId(final AlgorithmIdentifier algorithmId) throws NoSuchAlgorithmException {
        final ASN1ObjectIdentifier oid = algorithmId.getAlgorithm();
        return SUPPORTED_ALGORITHMS.values().stream().filter(algorithm -> Objects.nonNull(algorithm.getOID()) && Objects.equals(algorithm.getOID(), oid)).findFirst()
                .orElseThrow(() -> new NoSuchAlgorithmException("Unknown key pair algorithm type requested: " + oid));
    }
}
